#coding=utf-8
"""
API

U1@w2p: /gamex/init  --> matchID
    redirect:
    U1@nodejs: /gamex/<matchID>/creator/<playerID>/<secretkey>  # creator->init
    nodejs <--check_match_info-- w2p:  matchID, playerID, secretkey
    /gamex/check_match_info/<matchID>?player=3&secretkey=21543



U3@w2p: /gamex/proposed_matches
        U3@nodejs: /gamex/proposed_matches/in_a_que 


U3@w2p: /gamex/[join|leave]/matchID --> decide turn
    @nodejs (broadcast): /gamex/proposed_matches/update    

    if game can start (enough players), redirect for all:
    U3@nodejs: /gamex/matchId/play/p3ID/<secretkey>    # joined -> join -> play
    nodejs <--check_match_info-- w2p:  matchID, playerID, secretkey  --> player_list
    /gamex/check_match_info/<matchID>?player=3&secretkey=21543
    /gamex/check_match_info/<matchID>?player_list  --> [p1ID, p2ID, ..]     # instead of who_starts

nodejs: LOOP

nodejs --> w2py: GAMEOVER, moves_log, winner
    /gamex/update_match_info/<matchID>/finish?winner=12&moves_log=<serialised_json>

w2p possibility: gamex/leave/<machID>


LOOP:
/gamex/poll_situation/matchID --> board, whose_turn, ?gameOver
/gamex/make_turn/matchID (POST turn_info)
/gamex/disconect

"""
GAME_ID = 1
GAME = db.Games(GAME_ID)
#~ GAME_NAME = db.Games(GAME_ID).name #T("Kryžiukai-Nuliukai")
#~ GAME_SLUG = db.Games(GAME_ID).slug #tictactoe


import random
#~ from random import choice, randint
from datetime import datetime

#~ JSNODE_HOST = '88.222.149.115'  
JSNODE_HOST = '127.0.0.1'
GAME_SERVICE = 'http://%s:8080/' % JSNODE_HOST

#~ def list_values(rows, key):
    #~ """Helper for DAL to return list of values (instead list of dicts)"""
    #~ return [r.key for r in rows]
    
@auth.requires_login()
def get_player_id():
    """return player or team
    """
    query = (db.Players.external_id==auth.user.id) &  (db.Players.type_=='user') 
    if db( query ).isempty():
        playerID = db.Players.insert(type_='user', external_id=auth.user.id, name="%s %s"%(auth.user.first_name, auth.user.last_name))
    else:
        playerID = db( query ).select().first()['id']
        
    return playerID

def add_session_flash_msg(msg):
    session.flash = (session.flash or '') + msg
    
def gen_secretkey():
    return str( random.randint(100000, 9999999) )

###### proposed_matches
def proposed_matches():

    proposed_matches = db((db.Matches.started == None)).select(orderby=~db.Matches.proposed)
    joined_proposed_matches = [m.id for m in proposed_matches if get_player_id() in get_match_players(m)]
    proposed_maches_gameserver_links = { mID: get_game_link(mID, get_player_id(), 'join', content=T('žaisti (join)'))   for mID in joined_proposed_matches }

    ongoing_matches = db((db.Matches.started != None) & (db.Matches.finished == None)).select(orderby=db.Matches.game|~db.Matches.started)
    joined_ongoing_matches = [m.id for m in ongoing_matches if get_player_id() in get_match_players(m)]
    ongoing_maches_gameserver_links = { mID: get_game_link(mID, get_player_id(), 'join', content=T('žaisti'))   for mID in joined_ongoing_matches }

    finished_matches = db(db.Matches.finished != None) .select(orderby=db.Matches.game|~db.Matches.finished)
    #~ playerID = get_player_id()
    return locals()

def index():
    playerID = get_player_id()
    player_name = db.Players(get_player_id()).name
    return locals()
    
##### gamex/init 
def init(): # pasiūlyti mačą
    playerID = get_player_id()
    #~ gameID = db(db.Games.name==GAME.name).select().first()
    if get_ongoing_player_matches__cnt(playerID) > 0:
        add_session_flash_msg( T('Dar nebaigei žaisti kitų žaidimų - todėl negali jungtis prie naujų') )
    else:
        matchID = db.Matches.insert(game=GAME_ID, proposed=datetime.now())
        #~ return add_match_player(matchID, get_player_id(), 'init')
        add_match_player(matchID, playerID, 'init')
    redirect( URL(request.application, 'game', 'proposed_matches') )



##### gamex/join/<machID>
def join():  # norintiems dalyvauti
    match = get_current_match()
    if match.started:
        response.flash(T('Žaidimas jau prasidėjo, negali jame dalyvauti') )
        return 
        
    if not (get_player_id() in get_match_players(match) ):
        if add_match_player(match.id, get_player_id(), 'join'):
            session.flash = (session.flash or '') + T('Prisijungei prie %s' % match.proposed) 
        redirect( URL(request.application, 'game', 'proposed_matches') )
        
    else:
        response.flash = T('Jau esi žaidime')
        game_link = get_game_link(match.id, get_player_id(), 'join')
        return locals()
    

def get_game_link(matchID, playerID, action, content='game!'):
    actions_map2jsnode = {'init':'creator', 'join':'joined'}
    secretkey = db( (db.MatchPlayers.match_==matchID) & (db.MatchPlayers.player==playerID) ).select().first()['secretkey']
    game_url = GAME_SERVICE + '%s/%s/%s/%s/%s' % (GAME.slug, matchID, actions_map2jsnode[action], playerID, secretkey)
    game_link = A(content, _href=game_url)
    return game_link



def remove_player_from_proposed_matches(playerID):
    #~ db( (db.MatchPlayers.player==playerID) & (db.MatchPlayers.match_ == db.Matches.id) & (db.Matches.started == None) ).delete()
    rows = db( (db.MatchPlayers.player==playerID) & (db.MatchPlayers.match_ == db.Matches.id) & (db.Matches.started == None) ).select( db.MatchPlayers.id )
    for r in rows:
        del db.MatchPlayers[r.id]
    # also delete orphaned matches
    
    proposed_matches = db((db.Matches.started == None)).select()
    cnt = 0
    for match in proposed_matches:
        if match.MatchPlayers.count() == 0:
            match.delete_record()
            cnt += 1
    if cnt:
        add_session_flash_msg(T('Pašalinta %s ištuštėjusių siūlomų žaidimų mačų \n') % cnt)
            

def get_ongoing_player_matches(playerID):
    query = (db.MatchPlayers.player==playerID) & (db.MatchPlayers.match_ == db.Matches.id) & (db.Matches.started != None) & (db.Matches.finished == None)
    return db( query ).select(db.MatchPlayers.ALL)

def get_ongoing_player_matches__cnt(playerID):
    # check if player participates in ongoing matches
    ongoing_player_matches__cnt = db( (db.MatchPlayers.player==playerID) & (db.MatchPlayers.match_ == db.Matches.id) & (db.Matches.started != None) & (db.Matches.finished == None)).count()
    return  ongoing_player_matches__cnt
    

def add_match_player(matchID, playerID, action):
    match = db.Matches(matchID)
    if get_ongoing_player_matches__cnt(playerID) > 0:
        add_session_flash_msg( T('Dar nebaigei žaisti kitų žaidimų - todėl negali jungtis prie naujų') )
        return
                   
    if match.MatchPlayers.count() >= match.game.how_many_players:
        add_session_flash_msg( T('Šiam žaidimui jau yra pakankamai žaidėjų') )
        return 
    
    secretkey = gen_secretkey()
    #~ match[matchID] = '%s-%s'%(match.id, salt) # update
    db.MatchPlayers.insert( game_name=GAME.name,
                            secretkey=secretkey,
                            match_=matchID,
                            player=playerID,
                          )
    game_link = get_game_link(matchID, playerID, action)
    
    if match.MatchPlayers.count() == match.game.how_many_players:
        match.update_record(started = datetime.now());
        #~ remove_player_from_proposed_matches(playerID)
        for mp in match.MatchPlayers.select():
            remove_player_from_proposed_matches(mp.player)
        add_session_flash_msg( T('Žaidimas prasideda (esi atšauktas iš kitų laukiančių žaidimų)! \n'))
    else:
        add_session_flash_msg( T('Prisijngei...  Laukiam daugiau žaidėjų %s') % match.proposed )
        
    return locals()

def get_current_match():
    matchID = request.args(0, cast=int) or None # or redirect(URL('id not given'))
    return db.Matches(matchID) 

def get_player_ongoing_match(playerID):
    matchID = request.args(0, cast=int) or None # or redirect(URL('id not given'))
    return db.Matches(matchID) 

def remove_match_player(matchID, playerID):
    query_set = db((db.MatchPlayers.match_==matchID) & (db.MatchPlayers.player==playerID))
    if query_set.count() > 0:
        match  = db.Matches[matchID]

        # if player leaves proposed match
        if match.started == None :  
            query_set.delete()

        # if player leaves OnGoing match (he looses)
        if match.started != None  and match.finished == None:
            if match.MatchPlayers.count() == GAME.how_many_players: # jei dar yra kitų žaidėjų (ir TODO: žaidimo laikas nesibaigęs)
                outcome_msg = T('Pasidavei pirmas')
            elif match.MatchPlayers.count() > 1:
                outcome_msg = T('gal pasidavei (pasitraukei ne paskutinis)')
            else:
                outcome_msg = ''  
            add_session_flash_msg( T('Pasitraukei iš vykstančio žaidimo %s. %s') % (match.proposed, outcome_msg) )
            
            
            if match.MatchPlayers.count() > 1:
                if GAME.how_many_players == 2:
                    winner_id = db( (db.MatchPlayers.match_==matchID) & (db.MatchPlayers.player != playerID) ).select().first().player # first of others than current player ;) -- TODO -- update if
                else :
                    winner_id = None # TODO
            elif match.MatchPlayers.count() == 1:
                winner_id = playerID # TODO -- there should be no such cases, I guess?
                
            db.Matches(matchID).update_record( finished=datetime.now(), winner=winner_id, moves_log='TODO' )
        return True
   #~ response.flash(T('Tu atjungtas') )
   #~ return locals()

##### gamex/leave/<machID>
def leave():
    match = get_current_match()
    if remove_match_player( match.id, get_player_id() ):
        response.flash = T('Atsijungei nuo žaidimo %s') % match.proposed
    else:
        response.flash = T('Bandai atsijungti nuo žaidimo (%s), kuriame nedalyvauji') % match.proposed
    #~ return locals()
    #~ redirect(URL(request.application, 'game', 'proposed_matches'))
    session.flash=response.flash
    redirect(URL('proposed_matches'))
    
def get_match_players(match):
    rows = match.MatchPlayers.select()
    #~ rows = db(db.MatchPlayers.match_==match.id).select()
    #~ print rows
    players_ids = [int(r.player) for r in rows]
    return players_ids
    #~ return list_values(rows, 'player')



##########################################    
###                                    ###
###        JS NODE communication       ###
###                                    ###
##########################################    

def aprove_jsnode():
    return True  # TODO: change :)
    return request.client == JSNODE_HOST

##### update_match_info (called from jsnode)
def update_match_info():
    """informs, when the match starts/ends:
    gamex/update_match_info/<matchID>/start
    gamex/update_match_info/<matchID>/finish?winner=12&moves_log=<json>
    """
    if aprove_jsnode():
        match = get_current_match()
        
        if request.args(1)=='start':
            match.update_record(started=datetime.now())
            
        if request.args(1)=='finish':
            winnerID=int(request.vars['winner'])
            moves_log=request.vars['moves_log']
            match.update_record(finished=datetime.now(), winner=winnerID, moves_log=moves_log)
            

    
##### check_match_info (called from jsnode)
def check_match_info():
    """asks weather such player is really assigned to the match
    gamex/check_match_info/<matchID>?player=3&secretkey=21543
    gamex/check_match_info/<matchID>?who_starts # who starts?
    gamex/check_match_info/<matchID>?players  # players in turn order
    """
    if aprove_jsnode():
        # get json results
        from gluon.serializers import json

        match = get_current_match()
        playerID = request.vars.get('player', None)
        secretkey = request.vars.get('secretkey', None)
        #~ who_starts = request.vars.get('who_starts', None) or request.vars.get('whose_turn', None)
        #~ players = request.vars.get('get_players', None)

        # approve if player belongs to match
        if playerID and secretkey:
            playerID = int(playerID)
            if db( (db.MatchPlayers.match_==match) & 
                    (db.MatchPlayers.player==playerID)  &
                    (db.MatchPlayers.secretkey==secretkey)
                ).isempty():
                #~ return 0 # False
                raise HTTP(404) # False
            else:
                return json(1) # True

        # decide whose turn
        if 'who_starts' in request.vars or 'whose_turn' in request.vars:
            if not match.starting_player:
                starting_player = random.choice( get_match_players(match) )
                match.update_record(starting_player= starting_player)
            return json( match.starting_player )
            # TODO: if we play twice and in pairs -- there should be deeper logic:
            # second time they should exchange who_starts

        # return list of match players
        if 'get_players' in request.vars:
            return json( get_match_players(match) )

            
